package org.yong.springbootcmd.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.util.*;
import org.apache.commons.collections4.CollectionUtils;

/** Map集合工具 */
public class MapUtil {

  private static final Set<Class<?>> LIKE_ARRAY_CLASSES = Sets.newHashSet();

  static {
    LIKE_ARRAY_CLASSES.add(Boolean.class);
    LIKE_ARRAY_CLASSES.add(Character[].class);
    LIKE_ARRAY_CLASSES.add(Byte[].class);
    LIKE_ARRAY_CLASSES.add(Short[].class);
    LIKE_ARRAY_CLASSES.add(Integer[].class);
    LIKE_ARRAY_CLASSES.add(Long[].class);
    LIKE_ARRAY_CLASSES.add(Float[].class);
    LIKE_ARRAY_CLASSES.add(Double[].class);
    LIKE_ARRAY_CLASSES.add(boolean[].class);
    LIKE_ARRAY_CLASSES.add(char[].class);
    LIKE_ARRAY_CLASSES.add(byte[].class);
    LIKE_ARRAY_CLASSES.add(short[].class);
    LIKE_ARRAY_CLASSES.add(int[].class);
    LIKE_ARRAY_CLASSES.add(long[].class);
    LIKE_ARRAY_CLASSES.add(float[].class);
    LIKE_ARRAY_CLASSES.add(double[].class);

    LIKE_ARRAY_CLASSES.add(List.class);
    LIKE_ARRAY_CLASSES.add(Set.class);
    LIKE_ARRAY_CLASSES.add(JSONArray.class);
  }

  /**
   * 可迭代集合分组
   *
   * @param iterable 可迭代集合
   * @param kg 键生成器
   * @param <K> 集合键类型
   * @param <V> 集合值列表类型
   * @return 分组集合
   */
  public static <K, V> Map<K, List<V>> group(Iterable<V> iterable, KeyGenerator<K, V> kg) {
    Map<K, List<V>> ret = Maps.newLinkedHashMap();
    for (V v : iterable) {
      K k = kg.gen(v);
      List<V> vs = ret.get(k);
      if (CollectionUtils.isEmpty(vs)) ret.put(k, vs = Lists.newArrayList());
      vs.add(v);
    }
    return ret;
  }

  /**
   * Map集合过滤
   *
   * @param map Map集合
   * @param p 断言接口
   * @param <K> 集合Key类型
   * @param <V> 集合Value类型
   */
  public static <K, V> void filter(Map<K, V> map, Predicate<K, V> p) {
    // 由于 map.keySet() 返回map的键视图(只读)
    // 键视图在读取时会锁定map集合, 使其不允许修改
    // 通过List集合包装后 map.remove() 才有效
    for (K k : Lists.newArrayList(map.keySet())) if (!p.isRetain(k, map.get(k))) map.remove(k);
  }

  /**
   * 合并两个Map集合
   *
   * @param kMap 返回集合的键集合
   * @param vMap 返回集合的值集合
   * @param skipNullKey 跳过null键
   * @param skipNullValue 跳过null值
   * @param <MK> 合并键/值集合所依赖的键类型
   * @param <K> 返回集合的键类型
   * @param <V> 返回集合的值类型
   * @return 合并后集合对象
   */
  public static <MK, K, V> Map<K, V> mergeAsMapByKey(
      Map<MK, K> kMap, Map<MK, V> vMap, boolean skipNullKey, boolean skipNullValue) {
    Map<K, V> map = Maps.newLinkedHashMap();
    for (Map.Entry<MK, K> kme : kMap.entrySet()) {
      MK mk = kme.getKey();
      K k = kme.getValue();
      V v = vMap.get(mk);
      if ((skipNullKey && null == k) || (skipNullValue && null == v)) continue;
      map.put(k, v);
    }
    return map;
  }

  /**
   * TODO 未完成 展开对象为一级属性:
   *
   * <pre>
   *     {a:{foo:'bar'}} -> {a.foo:'bar'}
   * </pre>
   *
   * @param o 对象
   * @return 展开后对象
   */
  public static Map<String, Object> tiled(Object o) {
    Map<String, Object> ret = Maps.newHashMap();
    tiledSwitch("", o, ret);
    return ret;
  }

  /**
   * 校验目标字节码是否为复杂对象
   *
   * @param o 目标对象
   * @param excludeClass 需要排除的字节码
   * @return true-复杂对象字节码, false-原生对象字节码
   */
  private static boolean isPrimitive(Object o, Class<?>... excludeClass) {
    if (null == o) return true;

    Set<Class<?>> primitive = Sets.newHashSet();
    primitive.add(Boolean.class);
    primitive.add(Character.class);
    primitive.add(Byte.class);
    primitive.add(Short.class);
    primitive.add(Integer.class);
    primitive.add(Long.class);
    primitive.add(Float.class);
    primitive.add(Double.class);
    primitive.add(String.class);
    primitive.add(Date.class);
    primitive.add(null);
    if (null != excludeClass && 0 < excludeClass.length)
      primitive.addAll(Arrays.asList(excludeClass));

    return primitive.contains(o.getClass());
  }

  /**
   * 对象张开选择器
   *
   * @param prefixKey 前置属性
   * @param data 数据对象
   * @param store 平铺仓库
   */
  private static void tiledSwitch(String prefixKey, Object data, Map<String, Object> store) {
    if (null != data) {
      if (LIKE_ARRAY_CLASSES.contains(data.getClass())) tiledLikeArray(prefixKey, data, store);
      else tiledObject(prefixKey, data, store);
    }
  }

  /**
   * 展开对象
   *
   * @param prefixKey 前置属性名
   * @param data 数据对象
   * @param store 仓库
   */
  @SuppressWarnings("unchecked")
  private static void tiledObject(String prefixKey, Object data, Map<String, Object> store) {
    JSONObject jsonObject;
    if (data.getClass() == Map.class) jsonObject = new JSONObject((Map<String, Object>) data);
    else jsonObject = JSON.parseObject(JSON.toJSONString(data));

    String prefix = StringUtil.isBlank(prefixKey) ? "" : (prefixKey + ".");
    for (Map.Entry<String, Object> me : jsonObject.entrySet()) {
      String outKey = prefix + me.getKey();
      Object value = me.getValue();
      if (isPrimitive(value)) store.put(outKey, value);
      else tiledSwitch(outKey, value, store);
    }
  }

  /**
   * 展开列表
   *
   * @param prefixKey 前置属性名
   * @param data 数据对象
   * @param store 仓库
   */
  private static void tiledLikeArray(String prefixKey, Object data, Map<String, Object> store) {
    String prefix = prefixKey + "[";
    JSONArray json = JSON.parseArray(JSON.toJSONString(data));
    int index = 0;
    for (Object value : json) {
      String outKey = prefix + index + "]";
      if (isPrimitive(value)) store.put(outKey, value);
      else tiledSwitch(outKey, value, store);

      index++;
    }
  }

  /**
   * Map集合过滤断言
   *
   * @param <K>
   * @param <E>
   */
  public interface Predicate<K, E> {
    /** @return true-保留, false-删除 */
    boolean isRetain(K key, E value);
  }
}
